var svgNS = "http://www.w3.org/2000/svg";

function log(string)
{
	if (typeof(console)!="undefined")
	{
		if (console.log)
			console.log(string);
	}
}

function init(evt)
{
	if ( window.svgDoc == null )
		window.svgDoc = evt.target.ownerDocument;
	DataFetcher.init();
	DataFetcher.getGeneralData();
	DataFetcher.getData(null);
}

/*
This function returns the number of elements in an associative array.
It uses the number of elements in a blank Array as the number of elements to ignore in the actual array
*/
Array.prototype.size = function () {
	test=new Array();
	var numfunctions = 0;
	for (var k in test)
	{
		numfunctions++;
	}
	
	var l = 0;
	for (var k in this) {
		l++;
	}
	return l-numfunctions;
}

/*
This class stores all of the data for a certain view
It stores all of the objectids for a certain view, and how tall the object is at a certain timeid
*/
function Data(){
	this.objects=[];
	id:0;
};
Data.prototype={
	objects:[],
 	id:0,
 	/*
 	Set this particular data's id, acting as the parent id to all of the objectids
 	*/
 	setID: function(id)
 	{
 		this.id=id;
 	},
 	
 	/*
 	This function is used to set the data in this dataset.
 	Give it an objectid, and a mapping of timeid -> size
 	*/
	setObjectData: function(objectid, timedata)
	{
		this.objects[objectid]=timedata;
	},
 
 	/*
 	This function returns all of the objectids that are stored in this dataset
 	*/
	getObjectids: function()
	{
		var output=[];
		for (var objectid in this.objects)
		{
			if (typeof(this.objects[objectid])!="function")
			{
				output.push(objectid);
			}
			else
			{
				log("Not returning objectid("+objectid+") of type "+typeof(this.objects[objectid]));
			}
		}
		return output;
	},
 
 	/*
 	This function, given a timeid, returns all of the objects's sizes at that time
 	*/
 	getObjectSizes: function(timeid)
	{
		var out = Array();
		for (var objectid in this.objects)
		{
			//log("Trying to load the size for "+objectid+" at "+timeid);
			var size=this.getSizeData(objectid,timeid);
			//log("Found size "+size+" for objectid "+objectid);
			if (size!=null)
			{
				out[objectid]=size;
			}
		}
		if (out.size()>0)
		{
			return out;
		}
	},
 
 	/*
 	This function, given an objectid and a timeid, will return how big the object is at that time
 	*/
 	getSizeData: function(objectid, timeid, debug)
	{
		if (!debug) debug=0;
		if (this.objects)
		{
			if (this.objects[objectid])
			{
				if (this.objects[objectid][timeid])
				{
					var out=this.objects[objectid][timeid];
					if (typeof(out)!="function")
					{
						//log("Returning size "+out+" for object "+objectid+" at time "+timeid);
						return out;
					}
					else
					{
						if (debug>0) log("Not returning size "+out+" for object "+objectid+" at time "+timeid);
						
					}
				}
				else
				{
					if (debug>0) log("Not found: object "+objectid+" at time "+timeid);
				}
			}
		}
		if (debug>0) log("all objects: "+this.objects);
	},
	
	getMax: function()	// gets the maximum size of this dataset
	{
		var max = 0;
		for (var timeid in Times.getTimeids())
		{
			var sum = 0;
			var sizes = this.getObjectSizes(timeid);
			for (var objectid in sizes)
			{
				var size=sizes[objectid];
				if (size && typeof(size)!="function")
				{
					//log("Size is type "+typeof(size));
					//log("Adding size "+size);
					sum+=parseInt(size);
				}
			}
			max = Math.max(max,sum);
		}
		return max;
	},
 
}

var DataFetcher =
{
	dataURL:"",
	connected:1,
	
	init: function()
	{
		var loc = window.location.href.lastIndexOf('/');
		DataFetcher.dataURL=window.location.href.substring(0,loc)+"/sexygraph-svg.php";
	},
	
	getGeneralData: function()
	{
		var url=DataFetcher.dataURL+"?getnowtime";
		var request = new Json.Remote(url, {
			onComplete: function(jsonObj) {
				DataFetcher.receiveNowTime(jsonObj); },
			onFailure: function(jsonObj) {
				DataFetcher.disconnect(); },
			method: "post"
			}).send();
	},
	
	getData: function(id)
	{
		if (id==null)
		{
			DataFetcher.getGlobalData();
		}
		else
		{
			if (id.substring(0,4)=="host")
			{
				DataFetcher.getHostData(id.substring(4));
			}
		}
	},
	getGlobalData: function()
	{
		var url=DataFetcher.dataURL+"?getglobal&getscans";
		var request = new Json.Remote(url, {
			onComplete: DataFetcher.receiveGlobalData,
			onFailure: function(jsonObj) {
				DataFetcher.disconnect(); },
			method: "post"
        	}).send();
	},
	
	receiveNowTime: function(json)
	{
		Times.setNowTime(json.nowtime)
	},

	receiveGlobalData: function(json)
	{
		// save the time data
		Control.loadTimeData(json.scans);
		
		// save the view data for the top view
		data=new Data();
		data.setID(null);
		for (var objectid in json.global)
		{
			data.setObjectData(objectid,json.global[objectid]);
		};
		Datas.setData(null,data);
		
		// add the given names to the list of known names
		for (var objectid in json.globalnames)
		{
			ObjectNames.setName(objectid,json.globalnames[objectid]);
		}
		
		// add the order that the hosts are in
		ObjectOrders.setOrder(null,json.globalorder);
		
		
		setTimeout(function(){
			Control.loadData(null,data)}
		,1000);
		
		DataFetcher.connected=1;
	},
	
	disconnect: function(json)
	{
		DataFetcher.connected=0;
	}
};

var Control =
{
	loadTimeData: function(times)
	{
		for (var timeid in times)
		{
			if (typeof times[timeid] != 'function')
				Times.setTime(timeid, times[timeid]);
			//log("Found timeid "+timeid);
		}
		TimeOrders.loadTimeIds(times);	
	},
 
	loadData: function(id, data)
	{
		Datas.setData(id,data);
		Display.useData(id);
	},
}

var Datas =
{
	graphData:Array(),
	setData: function(id, data)
	{
		Datas.graphData[id]=data;
	},
 
 	getData: function(objectid)	// gets the data for a certain object
	{
		return Datas.graphData[objectid];
	}
}
	
var Times =
{
	timeData:Array(),
	nowTime:0,
	
	setNowTime: function(time)
	{
		Times.nowTime=parseInt(time);
	},
	
	getNowTime: function()
	{
		return Times.nowTime;
	},
	
	setTime: function(timeid, time)
	{
		Times.timeData[parseInt(timeid)]=parseInt(time);
	},
	
    getTimeids: function()
    {
            out = Array();
            for (var timeid in Times.timeData)
            {
                    out.push(parseInt(timeid));
            }
            return out;
    },

	getTime: function(timeid)
	{
		if (timeid<0)
		{
			return Times.timedata[0];
		}
		if (timeid>Times.timeData.length)
		{
			return Times.nowTime;
		}
		return Times.timeData[timeid];
	}
}

/**
This class stores the order of the timeids
*/
var TimeOrders =
{
	timeids:Array(),

	getTimeId:function(index)
	{
		return TimeOrders.timeids[index];
	},
	
	/**
	Given a mapping of timeid=>time, saves the proper order of it
	*/
	loadTimeIds:function(timeData)
	{
		for (timeid in timeData)
		{
			if (typeof timeid != 'function')
			{
				TimeOrders.timeids.push(timeid);
			}
		}
		TimeOrders.timeids=TimeOrders.timeids.sort(function(a,b){
			return timeData[a]-timeData[b];
		});
	},

	getTimeIndexes: function()	
	{
		return TimeOrders.timeids;
	}
	
}

/**
This class stores a mapping of objectids to object names
*/
var ObjectNames =
{
	// Associative array of objectid mapped to an object name
	objects:Array(),
	
	setName: function(objectid, objectName)
	{
		ObjectNames.objects[objectid]=objectName;
	},
	
	getName: function(objectid)
	{
		return ObjectNames.objects[objectid];
	},
	
	getIds: function()
	{
		var ret=Array();
		for (var objectid in ObjectNames.objects)
		{
			var name = ObjectNames.objects[objectid];
			if (typeof(name)=="string")
				ret.push(objectid);
		}
		log(ret);
		return ret;
	},
	
	getNames: function()
	{
		var ret=Array();
		for (var objectid in ObjectNames.objects)
		{
			var name = ObjectNames.objects[objectid];
			if (typeof(name)=="string")
				ret.push(name);
		}
		return ret;
	}
}

/*
Stores the order of the children of a Data view
The first element is on the top, the last is on the bottom
*/
var ObjectOrders =
{
	objects: Array(),
	
	/*
	ObjectID - the object's id
	children - the list of children ids, in order
	*/
	setOrder: function(objectid, children)
	{
		ObjectOrders.objects[objectid]=children;
	},
	
	getOrder: function(objectid)
	{
		return ObjectOrders.objects[objectid];
	}
}

var Display =
{

	_colors: Array(),

	// initializes the display driver
	init:function()
	{
		
	},
	
	reloadColors: function(dataid)
	{
		var defs=$('defs');
		var ids=ObjectNames.getIds();
		for (var x=0;x<ids.length;x++)
		{
			var objectid=ids[x];
			var wantedid=objectid;
			if (Display._colors[wantedid]==null)
			{
				var randomcolor=function(){return Math.ceil(Math.random()*256/16)*16};
				var red=randomcolor();
				var green=randomcolor();
				var blue=randomcolor();
				var color="rgb("+red+","+green+","+blue+")";
				
				var grad=Display._createGradient("color_"+wantedid,color);
				defs.appendChild(grad);
				Display._colors[wantedid]=color;
			}
			else
				log(document.getElementById(wantedid));
		}
	},
	
	useData:function(dataid)
	{
		Display.reloadColors();
		
		
		var data=Datas.getData(dataid);
		if (!Display.data)
		{
			var heights=Array();	// The heights at the top of the last host to be drawn, for each time period
			var starttime=Times.getTime(TimeOrders.getTimeId(0));
			var endtime=Times.getNowTime();
			//var endtime=Times.getTime(2)-starttime;
			
			Display.data=data;
			var max = data.getMax();
			
			$('plotarea').setProperty('viewBox',starttime+' -'+max+' '+(endtime-starttime)+' '+max);
			
			// create lines
			var lastline=null;
			data.lineNodes={};	// stores all of the pairs of lines, keyed by objectid
			
			var orders=ObjectOrders.getOrder(dataid);
			for (var objectindex=orders.length-1; objectindex>=0; objectindex--)
			{
				var linedata={}		// stores the pairs of lines for the current line
				
				var objectid=orders[objectindex];
				data.lineNodes[objectid]=[];
				
				var timeids = TimeOrders.getTimeIndexes();
				// figure out the locations of the nodes of the line
				for (var timeindex=0; timeindex<timeids.length; timeindex++)
				{
					timeid=parseInt(timeids[timeindex]);
					curtime=Times.getTime(timeid);
					if (typeof(curtime)=="undefined") continue
					
					if (lastline==null)
					{
						var bottom=0;
					}
					else
					{
						var bottom=lastline[timeid][1];
					}
					var cursize=data.getSizeData(objectid,timeid);
					if (cursize==null)
						cursize=0;
						
					var top=bottom+parseInt(cursize);
					
					// save this point for later reference
					var pointpair=[curtime,top]
					linedata[timeid]=(pointpair);
					
					
				}
				data.lineNodes[objectid]=linedata;
				lastline=linedata;
				
			}
			Display.drawLineGraph(dataid,"bezier2");
			
			
			var legend=$('legendarea');
			var orders=ObjectOrders.getOrder(dataid);
			y=0;
			for (var x=0; x<orders.length; x++)
			{
				var hostid=orders[x];
				var hostname=ObjectNames.getName(hostid);
				var color=Display._colors[hostid];
				
				var entry = Display._createLegendEntry(hostname,color);
				entry.setAttribute('y',y);
				
				legend.appendChild(entry);
				
				y+=parseInt(entry.getAttribute('height'));
			}
			
			Display.drawScales();
		}
	},
	
	drawLineGraph:function(dataid,linetype)
	{
		
		var bezierthreshold=1000;
		
		var data=Datas.getData(dataid);
		
		var objects=ObjectOrders.getOrder(dataid);
		//var minobject=objects.length-9;
		var minobject=0;

		var plot=document.createElementNS(svgNS,"g");	// temporary place to store all of the lines, then add to the document all at once
		for (var objectindex=minobject; objectindex<objects.length; objectindex++)
		{
			var objectid=objects[objectindex];
			log("Drawing objectid "+objectid+" at order "+objectindex);
			var line=document.createElementNS(svgNS,"path");
			line.id="line_"+objectid;
			line.setAttribute("fill",Display._colors[objectid]);			// solid fill
			//line.setAttribute("fill","url(#color_"+objectid+")");			// gradient-method solid fill
			
			var linedata=data.lineNodes[objectid];
			
			var linepath="";
			
			var timeids = Times.getTimeids();
			// draw the top nodes
			for (var timeindex=0; timeindex<timeids.length; timeindex++)
			{
				timeid=parseInt(timeids[timeindex]);
				curtime=Times.getTime(timeid);
				
				if (!linedata[timeid]) continue;
				
				var cury=linedata[timeid][1];
				
				var seg="";
				switch (linetype)	//linetype
				{
				case "lines":
					if (timeindex==0)
					{
						var command="M";
					}
					else
					{
						var command="L";
					}
					
					seg=command+curtime+" "+cury;
					break;
				case "bezier1":
					if (timeindex==0)
					{
						seg="M"+curtime+" "+cury;
					}
					else
					{
						var lasttimeid=parseInt(timeids[timeindex-1]);
						var lasttime=Times.getTime(lasttimeid);
						var x1=(curtime-lasttime)/3+lasttime;
						var y1=linedata[timeids[timeindex-1]][1];
						var x2=2*(curtime-lasttime)/3+lasttime;
						var y2=cury;
						var xdiff=Math.abs(curtime-lasttime)
						var ydiff=Math.abs(y1-y2)
						if (ydiff<bezierthreshold || xdiff<bezierthreshold)
							seg="L"+curtime+" "+cury;
						else
							seg="C"+x1+" "+y1+" "+x2+" "+y2+" "+curtime+" "+cury;
					}
					break;
				case "bezier2":
					if (timeindex==0)
					{
						seg="M"+curtime+" "+cury;
					}
					else
					{
						var lastertimeid=parseInt(timeids[timeindex-2]);
						var lastertime=Times.getTime(lastertimeid);
						var lasttimeid=parseInt(timeids[timeindex-1]);
						var lasttime=Times.getTime(lasttimeid);
						var nexttimeid=parseInt(timeids[timeindex+1]);
						var nexttime=Times.getTime(nexttimeid);
						
						var x1=0;
						var y1=0;
						var x2=0;
						var y2=0;
						
						if (lastertime)		// check the firsthalf of the dip to curve the lasthalf
						{
							var lastery=linedata[lastertimeid][1];
							var lasty=linedata[lasttimeid][1];
							if ((lastery-lasty)*(cury-lasty)>0)	// if lasty is above/below both lastery and cury
							{
								x1=(curtime-lasttime)/3+lasttime;
								y1=lasty;
								x2=curtime;
								y2=cury;
							}
						}
						if (nexttime)		// check the lasthalf of the dip to see if we need to curve the firsthalf
						{
							var lasty=linedata[lasttimeid][1];
							var nexty=linedata[nexttimeid][1];
							if ((lasty-cury)*(nexty-cury)>0)	// if cury is above/below both lasty and nexty
							{
								if (x1==0 && y1==0)
								{
									x1=lasttime;
									y1=lasty;
								}
								x2=2*(curtime-lasttime)/3+lasttime;
								y2=cury;
							}
						}
						
						if (x1>0 || y1>0 || x2>0 || y2>0)
						{
							seg="C"+x1+" "+y1+" "+x2+" "+y2+" "+curtime+" "+cury;
						}
						else
						{
							seg="L"+curtime+" "+cury;
						}
					}
					break;
				default:
					log("Can not draw linetype "+linetype);
				}
				linepath+=seg;
//				log("Adding segment "+seg);
			}
			
			// draw teh bottom of the line
			linepath+="V 0 ";
			curtime=Times.getTime(parseInt(timeids[0]));
			linepath+="H "+curtime;
			linepath+="Z";
			line.setAttribute("d",linepath);
			plot.appendChild(line);
		}
		$('invertedplotarea').appendChild(plot);
		
	},
	
	drawScales: function()
	{
		var data=Display.data;
		var y=0.5;
		var timeids = TimeOrders.getTimeIndexes();
		var timeid=parseInt(timeids[0]);
		var startx=Times.getTime(timeid);
		var endx=Times.getNowTime();
		var maxy=data.getMax();
		
		var lines=document.createElementNS(svgNS,"g");	// temporary place to store all of the lines, then add to the document all at once
		
		var interval=1024*1024*1024*1024;
		while (y*1024*1024*1024*1024 < maxy)
		{
			var terabytey=y*1024*1024*1024*1024;
			var path=document.createElementNS(svgNS,'path');
			path.setAttribute('d','M '+startx+','+terabytey+' H '+endx);
			path.setAttribute('stroke','white');
			path.setAttribute('stroke-width',interval/100);
			
			lines.appendChild(path);

			y+=0.5;
		}
		
		var x=endx;
		var interval=60*60*24
		while (x>startx)
		{
			var path=document.createElementNS(svgNS,'path');
			path.setAttribute('d','M '+x+','+0+' V '+maxy);
			path.setAttribute('stroke','white');
			path.setAttribute('stroke-width',interval/100);
		
			lines.appendChild(path);
				
			x-=interval;
		}
		
		$('invertedplotarea').appendChild(lines);
	},
	
	_createGradient:function(id,startcolor)
	{
		var grad = document.createElementNS(svgNS,'linearGradient');
		grad.id=id;
		grad.setAttribute('x1','0%');
		grad.setAttribute('x2','0%');
		grad.setAttribute('y1','0%');
		grad.setAttribute('y2','100%');
		
		var stop1 = document.createElementNS(svgNS,'stop');
		stop1.setAttribute('stop-color',startcolor);
		stop1.setAttribute('stop-opacity','1');
		stop1.setAttribute('offset','0');
		
		var stop2 = document.createElementNS(svgNS,'stop');
		stop1.setAttribute('stop-color',startcolor);
		stop1.setAttribute('stop-opacity','0.75');
		stop2.setAttribute('offset','1');
		
		grad.appendChild(stop1);
		//grad.appendChild(stop2);
		return grad;
	},
	
	// Creates a rectangle and returns it
	_createRectangle:function(left,top,right,bottom)
	{
		
		var rectangle = document.createElementNS(svgNS,'rect');
		rectangle=$(rectangle);
		//var rectangle = new Element('rect');
		rectangle.setProperties({
			x:left,
			y:bottom,
			width:(right-left),
			height:(top-bottom)});
		//rectangle.injectInside($('invertedplotarea'));
		//log("Drew a box, dimensions: "+left+" "+top+" "+right+" "+bottom);
		return rectangle;
	},
	
	_createLegendEntry:function(objectname, color)
	{
		var container = document.createElementNS(svgNS,'svg');
		container.setAttribute('width','600');
		container.setAttribute('height','16');
		
		var box=document.createElementNS(svgNS,'rect');
		box.setAttribute('fill',color);
		box.setAttribute('width','20');
		box.setAttribute('height','14');
		
		var text = document.createElementNS(svgNS,'text');
		text.setAttribute('x',25);
		text.setAttribute('y',10);
		text.setAttribute('font-size','12');
		text.appendChild(document.createTextNode(objectname));
		
		container.appendChild(box);
		container.appendChild(text);
		
		return container;
	}
}


